package main

import "fmt"

type Age uint // Age类型 值类型为uint

/**
这个方法表
*/
//func (age Age)String(i uint) string {
//	fmt.Println(age.String(i))
//	return age.String(i)
//}
// 方法
/**
方法一定会包含一个接收者
如下方法表示：
String方法的接收者是 Age类型，参数是一个uint类型，返回值是一个string类型
String方法是属于Age类型的方法
Age接收者是一个值类型
*/
// 此处的Age为值类型,它其实是原值的副本，可以看到它的指针地址和原值地址不同
func (age Age) String(i int) string {
	fmt.Println("值类型方法age指针地址:", &age, "值类型方法age值:", age, "参数的值：", i) // the age is 36 50
	//return age.String(i) // 如果没有终止条件，在这个方法内部调用自己会无限循环
	return "String 函数的返回值"
}

// 好像不可以使用基础类型作为接收者
//func (str string)String(i uint) string {
//	fmt.Println("the age is",age,i)
//	//return age.String(i) // 如果没有终止条件，在这个方法内部调用自己会无限循环
//	return "String 函数的返回值"
//}

// 使用值类型接收者和指针类型接收者
/**
pi := &i  // pi 为变量i的指针
*pi 表示指针引用的值
*/
/**

 */

// 值类型 就是以上方法的定义
// 指针类型 在类型名前面加上*  如 *Age
// 此处age为指针副本，表示是原来的指针的副本，原来的指针指向原值，一个新的指针指向原来的指针地址
func (age *Age) Modify(i int) string {
	fmt.Println("指针类型age：", age, "指针age指向的值：", *age, "参数：", i, "指向指针类型的指针的内存地址：", &age)
	// age 是一个指针类型，指向一个 Age 类型，所以再给它重新赋值的时候也必须是指针类型
	// 如果要修改指针类型的值，那么改指针的引用必须为一个相同类型
	*age = Age(i)
	fmt.Println("指针类型修改后的引用：", age, "修改后的值：", *age, "指向指针类型的指针的内存地址：", &age)
	return "指针类型方法的返回值"
}

/**
在调用方法的时候，传递的接收者本质上都是副本，只不过一个是这个值副本，一是指向这个值指针的副本。
指针具有指向原有值的特性，所以修改了指针指向的值，也就修改了原有的值。
我们可以简单地理解为值接收者使用的是值的副本来调用方法，而指针接收者使用实际的值来调用方法。
*/
func main() {
	age := Age(36)
	fmt.Println("开始age的指针为", &age)
	fmt.Println(age)
	age.String(50)
	age.Modify(60)
	age.String(50)

	/**
	示例中调用指针接收者方法的时候，使用的是一个值类型的变量，并不是一个指针类型，其实这里使用指针变量调用也是可以的，如下面的代码所示：
	*/
	(&age).Modify(70)
	age.String(90)
	/**
	这就是 Go 语言编译器帮我们自动做的事情：
	如果使用一个值类型变量调用指针类型接收者的方法，Go 语言编译器会自动帮我们取指针调用，以满足指针接收者的要求。
	同样的原理，如果使用一个指针类型变量调用值类型接收者的方法，Go 语言编译器会自动帮我们解引用调用，以满足值类型接收者的要求。
	总之，方法的调用者，既可以是值也可以是指针，不用太关注这些，Go 语言会帮我们自动转义，大大提高开发效率，同时避免因不小心造成的 Bug。
	不管是使用值类型接收者，还是指针类型接收者，要先确定你的需求：在对类型进行操作的时候是要改变当前接收者的值，还是要创建一个新值进行返回？这些就可以决定使用哪种接收者。
	*/
	fmt.Println("------方法赋值给变量使用-----")
	age2 := Age(25)
	strMethod := age2.String
	strMethod(10)
	fmt.Println("------方法赋值给变量使用-----测试无参方法")
	age3 := Age(100)
	str2 := age3.String2
	str2()
}

func (age Age) String2() string {
	fmt.Println("值类型方法age指针地址:", &age, "值类型方法age值:", age) // the age is 36 50
	//return age.String(i) // 如果没有终止条件，在这个方法内部调用自己会无限循环
	return "String 函数的返回值"
}
